#pragma once

#include "log.hpp"
#include "protocol.hpp"

#include <iostream>
#include <string>
#include <functional>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <signal.h>

using namespace std;

namespace server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR
    };

    static const uint16_t gport = 8080;

    static const int gbacklog = 5;

    typedef function<bool(const request &req, response &resp)> func_t;

    void handlerEntery(int sock, func_t func)
    {
        string inbuffer;    // 在多线程中，每个会话线程走到这，创建属于自己的"全局缓冲区"
        while (true)
        {
            // 1.读取 - 如何保证读到的是【一条完整】的request"字符串"？
            string text_str, req_str;
            // sleep(3);
            if (!recvText(sock, inbuffer, &text_str))
                return;

            // 走到这，一定有一个完整的报文了！ --->  "content_len"\r\n"123 + 456"\r\n
            // 所以进行解包(分离报头)
            cout << "带报头的报文 :\n" << text_str << endl;

            if (!deLength(text_str, &req_str))
                return;

            cout << "不带报头的请求 :\n" << req_str << endl;

            // 走到这，一定有一个完整的请求了！
            // 2.将读到的"字符串"进行反序列化 -> 得到一个request结构体
            request req;
            if (!req.deserialize(req_str))
                return;

            // 3.计算结果 -> 得到一个response结构体(核心业务逻辑)，我们要做解耦，不关心数据IO，以及序列化和反序列化等等细节
            response resp;
            func(req, resp);

            // 4.对response进行序列化
            string resp_str;
            resp.serialize(&resp_str);

            cout << "计算完成：序列化结果为  \n" << resp_str << endl;

            // 5.发送数据(发送之前添加报头)
            string sendMsg = enLength(resp_str);

            cout << "添加响应报文的报头为  \n" << sendMsg << endl;

            send(sock, sendMsg.c_str(), sendMsg.size(), 0);
            // write(sock, sendMsg.c_str(), sendMsg.size());
        }
    }

    // 注意：序列化和反序列化和协议定制没有关系

    class TcpServer
    {
    public:
        TcpServer(const uint16_t &port)
            : _listensock(-1), _port(port)
        {
        }

        ~TcpServer()
        {
        }

        void initServer()
        {
            // 1.创建socket文件套接字
            // _sock = socket(AF_INET, SOCK_STREAM, 0);
            _listensock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create socket err!");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success!");

            // 2.绑定网络信息
            struct sockaddr_in local;
            local.sin_family = PF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY; // 跟UDP一样，绑定任意IP
            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind socket err!");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success!");

            // 3.设置socket为监听状态
            if (listen(_listensock, gbacklog) < 0) // 第二个参数后面再学
            {
                logMessage(FATAL, "listen socket err!");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success : %d!", _listensock);
        }

        void run(func_t func)
        {
            for (;;)
            {
                // 4.获取新链接(注意：获取的前提是链接已经存在)
                // accept的返回值sock，将来才是和client通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                // 这俩参数是我们要知道谁来连我们的输入输出型参数
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next!");
                    continue; // 这里accept失败并不是什么大问题，直接重新获取新链接就行了
                }
                logMessage(NORMAL, "accept a new link success! get new sockfd : %d", sock);

                // version 2 - 多进程版(2种方法)

                pid_t id = fork();
                if (id == 0)
                {
                    // child
                    close(_listensock); // 子进程一定要关闭自己不需要的文件描述符

                    if (fork() > 0)
                        exit(0); // 子进程创建它的子进程(孙子进程)，然后子进程退了，下面waitpid()立马调用，就回收了
                    // 而孙子进程因为它的父进程退了，自己变孤儿进程了，所以被操作系统领养了，就不会导致内存泄漏

                    // 上面是一种做法，还有一种就是对SIGCHLD显式地调用signal()进行忽略
                    handlerEntery(sock, func);
                    close(sock);
                    exit(0);
                }

                close(sock); // 当然，父进程也要关闭自己不需要的sockfd

                pid_t ret = waitpid(id, nullptr, 0);
                // 非阻塞等待不好写，也不行，因为如果今天一百人来连我，然后后面就无人来了，就会阻塞在accept()那里，就没有回收子进程了
                if (ret > 0)
                {
                    logMessage(NORMAL, "wait success!");
                }
            }
        }

    private:
        int _listensock; // 这个套接字不是用来通信的，是用来监听的！
        int _port;
    };

} // namespace server
